"""
Base service layer implementation following SOLID principles.
"""

from typing import Any, Dict, List, Optional, Type, TypeVar, Generic
from abc import ABC, abstractmethod
from sqlalchemy.orm import Session

from ..logging import get_logger
from ..cache import cache_service
from .base import BaseModel
from .repository import BaseRepository

T = TypeVar('T', bound=BaseModel)


class IService(ABC, Generic[T]):
    """Service interface."""
    
    @abstractmethod
    def create(self, obj_in: Dict[str, Any]) -> T:
        """Create a new record."""
        pass
    
    @abstractmethod
    def get_by_id(self, id: Any) -> Optional[T]:
        """Get record by ID."""
        pass
    
    @abstractmethod
    def get_all(self, skip: int = 0, limit: int = 100) -> List[T]:
        """Get all records."""
        pass
    
    @abstractmethod
    def update(self, id: Any, obj_in: Dict[str, Any]) -> Optional[T]:
        """Update record."""
        pass
    
    @abstractmethod
    def delete(self, id: Any) -> bool:
        """Delete record."""
        pass


class BaseService(IService[T]):
    """Base service implementation."""
    
    def __init__(
        self,
        repository: BaseRepository[T],
        cache_enabled: bool = True,
        cache_ttl: int = 300
    ):
        self.repository = repository
        self.cache_enabled = cache_enabled
        self.cache_ttl = cache_ttl
        self.model_name = repository.model.__name__
        self.logger = get_logger(f"{self.model_name}Service")
    
    def _get_cache_key(self, key: str, *args) -> str:
        """Generate cache key."""
        return f"{self.model_name.lower()}:{key}:{':'.join(map(str, args))}"
    
    async def _get_from_cache(self, key: str) -> Optional[Any]:
        """Get data from cache."""
        if not self.cache_enabled:
            return None
        
        try:
            return await cache_service.get(key)
        except Exception as exc:
            self.logger.warning(
                "Cache get failed",
                key=key,
                error=str(exc)
            )
            return None
    
    async def _set_cache(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
        """Set data in cache."""
        if not self.cache_enabled:
            return
        
        try:
            await cache_service.set(key, value, expire=ttl or self.cache_ttl)
        except Exception as exc:
            self.logger.warning(
                "Cache set failed",
                key=key,
                error=str(exc)
            )
    
    async def _delete_cache(self, key: str) -> None:
        """Delete data from cache."""
        if not self.cache_enabled:
            return
        
        try:
            await cache_service.delete(key)
        except Exception as exc:
            self.logger.warning(
                "Cache delete failed",
                key=key,
                error=str(exc)
            )
    
    async def _invalidate_cache_pattern(self, pattern: str) -> None:
        """Invalidate cache by pattern."""
        if not self.cache_enabled:
            return
        
        try:
            await cache_service.invalidate_pattern(pattern)
        except Exception as exc:
            self.logger.warning(
                "Cache invalidation failed",
                pattern=pattern,
                error=str(exc)
            )
    
    def create(self, obj_in: Dict[str, Any]) -> T:
        """Create a new record."""
        try:
            # Validate input
            validated_data = self._validate_create_data(obj_in)
            
            # Create record
            record = self.repository.create(validated_data)
            
            # Invalidate related cache
            self._invalidate_create_cache(record)
            
            self.logger.info(
                "Record created",
                model=self.model_name,
                id=record.id
            )
            
            return record
            
        except Exception as exc:
            self.logger.error(
                "Error creating record",
                model=self.model_name,
                data=obj_in,
                error=str(exc),
                exc_info=True
            )
            raise
    
    async def get_by_id(self, id: Any) -> Optional[T]:
        """Get record by ID with caching."""
        cache_key = self._get_cache_key("id", id)
        
        # Try cache first
        cached_record = await self._get_from_cache(cache_key)
        if cached_record:
            return cached_record
        
        try:
            record = self.repository.get_by_id(id)
            
            # Cache the result
            if record:
                await self._set_cache(cache_key, record)
            
            return record
            
        except Exception as exc:
            self.logger.error(
                "Error getting record by ID",
                model=self.model_name,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def get_by_id_sync(self, id: Any) -> Optional[T]:
        """Get record by ID synchronously."""
        try:
            return self.repository.get_by_id(id)
        except Exception as exc:
            self.logger.error(
                "Error getting record by ID",
                model=self.model_name,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    async def get_all(self, skip: int = 0, limit: int = 100) -> List[T]:
        """Get all records with caching."""
        cache_key = self._get_cache_key("all", skip, limit)
        
        # Try cache first
        cached_records = await self._get_from_cache(cache_key)
        if cached_records:
            return cached_records
        
        try:
            records = self.repository.get_all(skip=skip, limit=limit)
            
            # Cache the result
            await self._set_cache(cache_key, records, ttl=60)  # Shorter TTL for lists
            
            return records
            
        except Exception as exc:
            self.logger.error(
                "Error getting all records",
                model=self.model_name,
                skip=skip,
                limit=limit,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def update(self, id: Any, obj_in: Dict[str, Any]) -> Optional[T]:
        """Update record."""
        try:
            # Validate input
            validated_data = self._validate_update_data(obj_in)
            
            # Update record
            record = self.repository.update(id, validated_data)
            
            if record:
                # Invalidate related cache
                self._invalidate_update_cache(record)
                
                self.logger.info(
                    "Record updated",
                    model=self.model_name,
                    id=id
                )
            
            return record
            
        except Exception as exc:
            self.logger.error(
                "Error updating record",
                model=self.model_name,
                id=id,
                data=obj_in,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def delete(self, id: Any) -> bool:
        """Delete record."""
        try:
            success = self.repository.delete(id)
            
            if success:
                # Invalidate related cache
                self._invalidate_delete_cache(id)
                
                self.logger.info(
                    "Record deleted",
                    model=self.model_name,
                    id=id
                )
            
            return success
            
        except Exception as exc:
            self.logger.error(
                "Error deleting record",
                model=self.model_name,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def soft_delete(self, id: Any) -> bool:
        """Soft delete record."""
        try:
            success = self.repository.soft_delete(id)
            
            if success:
                # Invalidate related cache
                self._invalidate_delete_cache(id)
                
                self.logger.info(
                    "Record soft deleted",
                    model=self.model_name,
                    id=id
                )
            
            return success
            
        except Exception as exc:
            self.logger.error(
                "Error soft deleting record",
                model=self.model_name,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def exists(self, id: Any) -> bool:
        """Check if record exists."""
        try:
            return self.repository.exists(id)
        except Exception as exc:
            self.logger.error(
                "Error checking record existence",
                model=self.model_name,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def count(self, filters: Optional[Dict[str, Any]] = None) -> int:
        """Count records."""
        try:
            return self.repository.count(filters)
        except Exception as exc:
            self.logger.error(
                "Error counting records",
                model=self.model_name,
                filters=filters,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def find_by(self, **kwargs) -> List[T]:
        """Find records by criteria."""
        try:
            return self.repository.find_by(**kwargs)
        except Exception as exc:
            self.logger.error(
                "Error finding records",
                model=self.model_name,
                criteria=kwargs,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def find_one_by(self, **kwargs) -> Optional[T]:
        """Find one record by criteria."""
        try:
            return self.repository.find_one_by(**kwargs)
        except Exception as exc:
            self.logger.error(
                "Error finding record",
                model=self.model_name,
                criteria=kwargs,
                error=str(exc),
                exc_info=True
            )
            raise
    
    async def paginate(
        self,
        page: int = 1,
        size: int = 20,
        filters: Optional[Dict[str, Any]] = None,
        order_by: Optional[str] = None,
        order_desc: bool = False
    ) -> Dict[str, Any]:
        """Paginate records with caching."""
        cache_key = self._get_cache_key(
            "paginate", page, size, 
            str(filters) if filters else "none",
            order_by or "none", order_desc
        )
        
        # Try cache first
        cached_result = await self._get_from_cache(cache_key)
        if cached_result:
            return cached_result
        
        try:
            result = self.repository.paginate(
                page=page,
                size=size,
                filters=filters,
                order_by=order_by,
                order_desc=order_desc
            )
            
            # Cache the result
            await self._set_cache(cache_key, result, ttl=60)
            
            return result
            
        except Exception as exc:
            self.logger.error(
                "Error paginating records",
                model=self.model_name,
                page=page,
                size=size,
                filters=filters,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def bulk_create(self, objects: List[Dict[str, Any]]) -> List[T]:
        """Bulk create records."""
        try:
            # Validate all objects
            validated_objects = [
                self._validate_create_data(obj) for obj in objects
            ]
            
            records = self.repository.bulk_create(validated_objects)
            
            # Invalidate related cache
            self._invalidate_bulk_cache()
            
            self.logger.info(
                "Bulk create completed",
                model=self.model_name,
                count=len(records)
            )
            
            return records
            
        except Exception as exc:
            self.logger.error(
                "Error in bulk create",
                model=self.model_name,
                count=len(objects),
                error=str(exc),
                exc_info=True
            )
            raise
    
    def _validate_create_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Validate data for creation."""
        # Override in subclasses for specific validation
        return data
    
    def _validate_update_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Validate data for update."""
        # Override in subclasses for specific validation
        return data
    
    async def _invalidate_create_cache(self, record: T) -> None:
        """Invalidate cache after create."""
        pattern = f"{self.model_name.lower()}:*"
        await self._invalidate_cache_pattern(pattern)
    
    async def _invalidate_update_cache(self, record: T) -> None:
        """Invalidate cache after update."""
        # Invalidate specific record cache
        cache_key = self._get_cache_key("id", record.id)
        await self._delete_cache(cache_key)
        
        # Invalidate list caches
        pattern = f"{self.model_name.lower()}:all:*"
        await self._invalidate_cache_pattern(pattern)
        
        pattern = f"{self.model_name.lower()}:paginate:*"
        await self._invalidate_cache_pattern(pattern)
    
    async def _invalidate_delete_cache(self, id: Any) -> None:
        """Invalidate cache after delete."""
        # Invalidate specific record cache
        cache_key = self._get_cache_key("id", id)
        await self._delete_cache(cache_key)
        
        # Invalidate list caches
        pattern = f"{self.model_name.lower()}:*"
        await self._invalidate_cache_pattern(pattern)
    
    async def _invalidate_bulk_cache(self) -> None:
        """Invalidate cache after bulk operations."""
        pattern = f"{self.model_name.lower()}:*"
        await self._invalidate_cache_pattern(pattern)


class CachedService(BaseService[T]):
    """Service with enhanced caching capabilities."""
    
    def __init__(
        self,
        repository: BaseRepository[T],
        cache_enabled: bool = True,
        cache_ttl: int = 300,
        list_cache_ttl: int = 60,
        enable_write_through: bool = False
    ):
        super().__init__(repository, cache_enabled, cache_ttl)
        self.list_cache_ttl = list_cache_ttl
        self.enable_write_through = enable_write_through
    
    async def get_or_create(
        self,
        defaults: Dict[str, Any],
        **kwargs
    ) -> tuple[T, bool]:
        """Get or create record."""
        try:
            # Try to find existing record
            record = self.repository.find_one_by(**kwargs)
            if record:
                return record, False
            
            # Create new record
            create_data = {**kwargs, **defaults}
            record = self.create(create_data)
            return record, True
            
        except Exception as exc:
            self.logger.error(
                "Error in get_or_create",
                model=self.model_name,
                criteria=kwargs,
                defaults=defaults,
                error=str(exc),
                exc_info=True
            )
            raise
    
    async def update_or_create(
        self,
        defaults: Dict[str, Any],
        **kwargs
    ) -> tuple[T, bool]:
        """Update or create record."""
        try:
            # Try to find existing record
            record = self.repository.find_one_by(**kwargs)
            if record:
                # Update existing record
                updated_record = self.update(record.id, defaults)
                return updated_record, False
            
            # Create new record
            create_data = {**kwargs, **defaults}
            record = self.create(create_data)
            return record, True
            
        except Exception as exc:
            self.logger.error(
                "Error in update_or_create",
                model=self.model_name,
                criteria=kwargs,
                defaults=defaults,
                error=str(exc),
                exc_info=True
            )
            raise